package com.hc.mybatis.generator.plugins;

import com.hc.mybatis.generator.plugins.internal.CustomeFullyQualifiedJavaType;
import com.hc.mybatis.generator.plugins.utils.CommentTools;
import com.hc.mybatis.generator.plugins.utils.XmlElementGeneratorTools;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.OutputUtilities;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
import org.mybatis.generator.config.GeneratedKey;
import org.mybatis.generator.internal.util.StringUtility;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @Author: huangchuang
 * @CreateTime: 2022-12-07 17:37
 * @Description:
 */
public class BatchInsertPlugin extends PluginAdapter {

    public static final String INSERT_BATCH_STATEMENT_ID = "insertBatch";
    private static final int NEW_LINE_CHARACTER_LIMIT = 80;
    private static final String TIMESTAMP_JDBCTYPENAME = "TIMESTAMP";

    @Override
    public boolean validate(List<String> list) {
        if (StringUtility.stringHasValue(this.getContext().getTargetRuntime()) && !"MyBatis3".equalsIgnoreCase(this.getContext().getTargetRuntime())) {
            System.out.println("itfsw:插件" + this.getClass().getTypeName() + "要求运行targetRuntime必须为MyBatis3！");
            return false;
        } else {
            return true;
        }
    }

    @Override
    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        Method method = new Method(INSERT_BATCH_STATEMENT_ID);
        //出参
        FullyQualifiedJavaType returnType = FullyQualifiedJavaType.getIntInstance();
        method.setReturnType(returnType);

        //入参
        FullyQualifiedJavaType type = FullyQualifiedJavaType.getNewListInstance();
        FullyQualifiedJavaType parameterType;
        if (introspectedTable.getRules().generateRecordWithBLOBsClass()) {
            parameterType = new FullyQualifiedJavaType(introspectedTable.getRecordWithBLOBsType());
        } else {
            parameterType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
        }
        type.addTypeArgument(parameterType);
        method.addParameter(new Parameter(type, "list"));

        CommentTools.addGeneralMethodComment(method, "批量保存数据");
        interfaze.addMethod(method);
        //导入依赖包
        interfaze.addImportedType(FullyQualifiedJavaType.getNewListInstance());
        System.out.println("itfsw(批量保存数据):" + interfaze.getType().getShortName() + "增加" + INSERT_BATCH_STATEMENT_ID + "方法。");
        return true;
    }

    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        XmlElement batchInsertElement = new XmlElement("insert");
        batchInsertElement.addAttribute(new Attribute("id", INSERT_BATCH_STATEMENT_ID));
        batchInsertElement.addAttribute(new Attribute("parameterType", FullyQualifiedJavaType.getNewListInstance().getFullyQualifiedName()));
        context.getCommentGenerator().addComment(batchInsertElement);

        GeneratedKey gk = introspectedTable.getGeneratedKey();
        if (gk != null) {
            System.out.println("gk1:" + gk.toString());
            IntrospectedColumn introspectedColumn = introspectedTable.getColumn(gk.getColumn());
            if (introspectedColumn != null) {
                if (gk.isJdbcStandard()) {
                    batchInsertElement.addAttribute(new Attribute("useGeneratedKeys", "true"));
                    batchInsertElement.addAttribute(new Attribute("keyProperty", introspectedColumn.getJavaProperty()));
                } else {
                    batchInsertElement.addElement(XmlElementGeneratorTools.getSelectKey(introspectedColumn, gk));
                }
            }
        }

        StringBuilder insertClause = new StringBuilder();
        StringBuilder valuesClause = new StringBuilder();

        insertClause.append("insert into ");
        insertClause.append(introspectedTable.getFullyQualifiedTableNameAtRuntime());
        insertClause.append(" (");

        List<String> valuesClauses = new ArrayList<>();
        valuesClauses.add("values ");
        valuesClauses.add("<foreach collection=\"list\" separator=\",\" index=\"index\" item=\"item\">");
        valuesClauses.add("  (");

        Iterator<IntrospectedColumn> iter = introspectedTable.getAllColumns().iterator();
        while (iter.hasNext()) {
            IntrospectedColumn introspectedColumn = iter.next();
            System.out.println("introspectedColumn: " + introspectedColumn.toString());
            if (introspectedColumn.isIdentity()) {
                // cannot set values on identity fields
                continue;
            }

            insertClause.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));

            if (introspectedColumn.getDefaultValue() == null) {
                getValueClauseWhenNoDefaultValue(valuesClause, valuesClauses, iter, introspectedColumn, 1);
            } else {
                getChooseString(valuesClause, valuesClauses, introspectedColumn, iter);
            }

            appendCommaWhenHasNext(insertClause, iter);

            if (insertClause.length() > NEW_LINE_CHARACTER_LIMIT) {
                batchInsertElement.addElement(new TextElement(insertClause.toString()));
                insertClause.setLength(0);
                OutputUtilities.xmlIndent(insertClause, 1);
            }
        }

        insertClause.append(')');
        batchInsertElement.addElement(new TextElement(insertClause.toString()));

        valuesClause.append(')');
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);
        valuesClause.append("</foreach>");
        valuesClauses.add(valuesClause.toString());

        for (String clause : valuesClauses) {
            batchInsertElement.addElement(new TextElement(clause));
        }

        if (context.getPlugins().sqlMapInsertElementGenerated(batchInsertElement, introspectedTable)) {
            document.getRootElement().addElement(batchInsertElement);
        }
        System.out.println("itfsw(批量保存数据):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加" + INSERT_BATCH_STATEMENT_ID + "方法(" + (introspectedTable.hasBLOBColumns() ? "有" : "无") + "Blob类型))。");
        return true;
    }

    private void getValueClauseWhenNoDefaultValue(StringBuilder valuesClause, List<String> valuesClauses,
                                                  Iterator<IntrospectedColumn> iter, IntrospectedColumn introspectedColumn, int indentLevel) {
        OutputUtilities.xmlIndent(valuesClause, indentLevel);
        valuesClause.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn));
        insertItemAfterLeftBrace(valuesClause);
        appendCommaWhenHasNext(valuesClause, iter);
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);
    }

    private void getChooseString(StringBuilder valuesClause, List<String> valuesClauses,
                                 IntrospectedColumn introspectedColumn, Iterator iter) {
        OutputUtilities.xmlIndent(valuesClause, 1);
        valuesClause.append("<choose>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("<when test=\"item.");
        valuesClause.append(introspectedColumn.getJavaProperty(null));
        valuesClause.append(" != null\" >");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        getValueClauseWhenNoDefaultValue(valuesClause, valuesClauses, iter, introspectedColumn, 3);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("</when>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("<otherwise>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 3);
//        System.out.println(">>>>>>introspectedColumn.getJdbcType:" + introspectedColumn.getJdbcType()
//                + ", introspectedColumn.getJdbcTypeName:" + introspectedColumn.getJdbcTypeName()
//                + ",introspectedColumn.getDefaultValue:" + introspectedColumn.getDefaultValue()
//                + ",introspectedColumn.equals:" + TIMESTAMP_JDBCTYPENAME.equals(introspectedColumn.getJdbcTypeName())
//                + ",introspectedColumn.isJDBCDateColumn:" + introspectedColumn.isJDBCDateColumn());
        if (TIMESTAMP_JDBCTYPENAME.equals(introspectedColumn.getJdbcTypeName())) {
            valuesClause.append("now()");
        } else {
            valuesClause.append(wrapQuotaPair(introspectedColumn.getDefaultValue()));
        }
        appendCommaWhenHasNext(valuesClause, iter);
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("</otherwise>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 1);
        valuesClause.append("</choose>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);
    }

    private void appendCommaWhenHasNext(StringBuilder insertClause, Iterator<IntrospectedColumn> iter) {
        if (iter.hasNext()) {
            insertClause.append(", ");
        }
    }

    private void insertItemAfterLeftBrace(StringBuilder valuesClause) {
        int index = valuesClause.indexOf("{");
        if (index != -1) {
            valuesClause.insert(index + 1, "item.");
        }
    }

    private String wrapQuotaPair(String defaultValue) {
        return "'" + defaultValue + "'";
    }

}
